﻿using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using log4net;
using StockFinder.Indicators.Day;
using StockFinder.Indicators.Day.ClosePrice;
using StockFinder.Indicators.Day.Volume;
using StockFinder.Model;
using StockFinder.Model.Constants;
using StockFinder.StockLists;
using System;

namespace StockFinder.TradingSystems
{
    /*
     * Bounces off 50 SMA near 52 week high with trailing stop
     * --------------------------------------------------------
     * Entry: bounce off 50 SMA with relative volume
     * Exit: % trailing stop
     * 
     */

    public class BounceOffMovingAverageTradingSystem : ITradingSystem
    {
        private static readonly ILog Log = LogManager.GetLogger(typeof(BounceOffMovingAverageTradingSystem));        

        #region ITradingSystem Members

        public TradingSystemResult Run(TradingSystemParameters parameters, IStockList stockList)
        {
            var tradingSystemResult = new TradingSystemResult();
            var stopWatch = new Stopwatch();
            var index = 0;

            var stockListCount = stockList.Count;

            stopWatch.Start();

            foreach (var symbol in stockList)
            {
                index++;

                Log.DebugFormat("{0}", symbol.Name);

                if (symbol.Prices == null || symbol.Prices.Count == 0)
                {
                    Log.DebugFormat("Returned empty prices");
                    continue;
                }

                Log.DebugFormat("Loaded {0} prices", symbol.Prices.Count);

                var tradesForCurrentStock = Run(parameters, symbol);

                tradingSystemResult.Trades.AddRange(tradesForCurrentStock.Trades);

                Log.DebugFormat("{0}% completed", Math.Round(((index / (double)stockListCount) * 100), 2));
            }

            Log.DebugFormat("Total time taken: {0}:{1} (m:s)",
                stopWatch.Elapsed.Minutes,
                stopWatch.Elapsed.Seconds);

            return tradingSystemResult;
        }

        public TradingSystemResult Run(TradingSystemParameters parameters, Symbol symbol)
        {
            Log.DebugFormat("Running KevinPietersenTradingSystem system on {0}", symbol.Name);

            #region Parameters check

            var systemParameters = parameters as BounceOffMovingAverageTradingSystemParameters;
            if (systemParameters == null)
            {
                throw new ArgumentException("'parameters' must be of type KevinPietersenTradingSystemParameters. Is either null or invalid cast.");
            }

            var percentWithinYearHighThreshold = 1-(systemParameters.PercentFromYearHigh/100);
            var monthlyAverageVolumeThreshold = 1+(systemParameters.MonthlyAverageRelativeVolumeThreshold/100);
            var percentFrom50SmaThreshold = 1+(systemParameters.PercentFrom50SmaThreshold/100);
            var percentTrailingStopLossMultiplier = 1 - (systemParameters.PercentTrailingStopLossMultiplier / 100);

            #endregion

            #region Apply technical indicators

            SimpleMovingAverageClosePriceDayIndicator.Fifty.ApplyIndicator(symbol.Prices);
            SimpleMovingAverageDailyVolumeDayIndicator.Month.ApplyIndicator(symbol.Prices);

            #endregion

            #region Position details

            var result = new TradingSystemResult();
            Trade trade = null;
            var openNewPosition = false;
            var adjustTrailingStopLoss = false;

            #endregion

            #region Local variables
            var stopWatch = new Stopwatch();
            stopWatch.Start();
            var priceCount = symbol.Prices.Count;
            var pricesArray = symbol.Prices.OrderBy(p => p.PriceDate).ToArray();
            #endregion
            
            var indiciatorLookback = Math.Max(SimpleMovingAverageClosePriceDayIndicator.Fifty.LookbackPeriod, 
                SimpleMovingAverageDailyVolumeDayIndicator.Month.LookbackPeriod) - 1;

            #region System loop

            //loop prices
            for (var i = 0; i < priceCount; i++)
            {
                #region Daily variables
                //var samplePrices = pricesArray.Take(i).ToList();                
                var todayPrice = pricesArray[i];
                #endregion

                #region Indicator history check

                //need enough indicator history
                if (i < indiciatorLookback) continue;                    

                #endregion

                #region Pre-market checks

                #region Close the position

                //do we need to close open position?
                if (trade != null)
                {
                    //use greater of trailing and hard
                    var stopLossToCheck = (trade.HardStopLoss > trade.TrailingStopLoss)
                                              ? trade.HardStopLoss
                                              : trade.TrailingStopLoss;

                    //did we break the stop loss?
                    var yesterdayPrice = pricesArray[i - 1];

                    if (yesterdayPrice.AdjustedClose < stopLossToCheck)
                    {
                        trade.ExitDate = todayPrice.PriceDate;
                        trade.ExitPrice = todayPrice.AdjustedOpen;
                        trade.IsOpen = false;
                        trade = null; //reset position
                    }       
                }

                #endregion

                #region Adjust trailing stop loss

                //do we need to adjust trailing stop loss?
                if (adjustTrailingStopLoss)
                {
                    if (trade != null)
                    {
                        var yesterdayPrice = pricesArray[i - 1];

                        trade.HighestClose = yesterdayPrice.AdjustedClose;
                        trade.TrailingStopLoss = yesterdayPrice.AdjustedClose * percentTrailingStopLossMultiplier;
                    }

                    adjustTrailingStopLoss = false;
                }

                #endregion

                #region Open new position

                //do we need to open a position today?
                if (openNewPosition)
                {
                    var yesterdayPrice = pricesArray[i - 1];

                    //set position flags
                    openNewPosition = false;
                    trade = new Trade
                    {
                        Symbol = symbol.Name,
                        IsOpen = true,
                        EntryDate = todayPrice.PriceDate,
                        EntryPrice = todayPrice.AdjustedOpen,
                        HardStopLoss = todayPrice.AdjustedOpen*percentTrailingStopLossMultiplier,
                        TrailingStopLoss = todayPrice.AdjustedOpen * percentTrailingStopLossMultiplier,
                        HighestClose = todayPrice.AdjustedOpen,
                    };

                    //any technical details want to keep 
                    var breakoutDayMonthlyVolume = yesterdayPrice.DayIndicators[SimpleMovingAverageDailyVolumeDayIndicator.Month.IndicatorName];
                    var confirmationDayMonthlyVolume = todayPrice.DayIndicators[SimpleMovingAverageDailyVolumeDayIndicator.Month.IndicatorName];

                    trade.StateAtEntry["MonthAverageVolumeOnBreakoutDay"] =
                        yesterdayPrice.DayIndicators[SimpleMovingAverageDailyVolumeDayIndicator.Month.IndicatorName];

                    trade.StateAtEntry["AbsoluteVolumeOnBreakoutDay"] = yesterdayPrice.Volume;
                    var relativeVolume = 0m;
                    if (yesterdayPrice.Volume > 0)
                    {
                        relativeVolume = ((yesterdayPrice.Volume - breakoutDayMonthlyVolume) / breakoutDayMonthlyVolume) * 100;
                    }
                    trade.StateAtEntry["RelativeVolumeOnBreakoutDay"] = relativeVolume;

                    trade.StateAtEntry["DaysOfPricesBeforeEntry"] = i;
                    trade.StateAtEntry["TotalDaysOfPrices"] = priceCount;

                    trade.StateAtEntry["PriceChangeOnConfirmationDay"] = ((todayPrice.AdjustedClose -
                                                                         yesterdayPrice.AdjustedClose) /
                                                                        yesterdayPrice.AdjustedClose) * 100;

                    trade.StateAtEntry["AbsoluteVolumeOnConfirmationDay"] = todayPrice.Volume;
                    var relativeConfVolume = 0m;
                    if (todayPrice.Volume > 0)
                    {
                        relativeConfVolume = ((todayPrice.Volume - confirmationDayMonthlyVolume) / confirmationDayMonthlyVolume) * 100;
                    }
                    trade.StateAtEntry["RelativeVolumeOnConfirmationDay"] = relativeConfVolume;

                    result.Trades.Add(trade);
                }

                #endregion

                #endregion

                #region After market checks

                if (trade == null)
                {
                    //make sure above minimum price
                    if (todayPrice.AdjustedClose >= systemParameters.MinimumPriceThreshold)
                    {
                        if (todayPrice.Volume >= systemParameters.MinimumVolumeThreshold)
                        {
                            //1. check monthly volume is above threshold
                            var avgVolume = todayPrice.DayIndicators[SimpleMovingAverageDailyVolumeDayIndicator.Month.IndicatorName];
                            var relVolumeThreshold = avgVolume * monthlyAverageVolumeThreshold;
                            if (todayPrice.Volume >= relVolumeThreshold)
                            {
                                //2. check whether above 50 SMA but below deviation from 50 sma threshold
                                var sma50 = todayPrice.DayIndicators[SimpleMovingAverageClosePriceDayIndicator.Fifty.IndicatorName];
                                var sma50Threshold = sma50 * percentFrom50SmaThreshold;
                                if ((todayPrice.AdjustedClose >= sma50) && (todayPrice.AdjustedClose <= sma50Threshold))
                                {
                                    //3. check distance from 52 week high
                                    var dateYearAgo = todayPrice.PriceDate.AddYears(-1);
                                    var yearHigh =
                                        pricesArray.Where(p => p.PriceDate > dateYearAgo && p.PriceDate <= todayPrice.PriceDate).Max(
                                            p => p.AdjustedClose);

                                    var yearHighThreshold = yearHigh * percentWithinYearHighThreshold;
                                    if (todayPrice.AdjustedClose >= yearHighThreshold)
                                    {
                                        openNewPosition = true;
                                    }
                                }
                            }
                        }
                    }      
                }
                else
                {
                    //if we are already in a trade, and today's close is higher than any previous close in
                    //the trade, we need to adjust the trailing stop to account for this
                    if (todayPrice.AdjustedClose > trade.HighestClose)
                    {
                        adjustTrailingStopLoss = true;
                    }
                }
                            
                #endregion
            }

            #endregion

            #region Open position check
            if (result.Trades.Count > 0)
            {
                trade = result.Trades.Last();
                var todayPrice = pricesArray.Last();

                //set exit as close price if open position
                if (trade.ExitPrice == 0)
                {
                    trade.ExitDate = todayPrice.PriceDate;

                    //pesimistic to use exit price but better best of underestimating
                    trade.ExitPrice = (trade.HardStopLoss > trade.TrailingStopLoss)
                                           ? trade.HardStopLoss
                                           : trade.TrailingStopLoss;
                }
            }
            #endregion         

            return result;
        }

        #endregion
    }
}
